package com.nage.multiplayer;

import java.io.ByteArrayOutputStream;

import com.nage.input.Input;
import com.nage.utils.ByteArrayBuilder;

/**
 * Contains all the fields of a standard input object, but provides
 * methods for converting to a stream of bytes that can be sent
 * across a network, and for returning to an input object from 
 * a stream of bytes.
 * 
 * @author lukeo05
 *
 */
public class RemoteInput extends Input {

//	private static final String PROTOCOL_VERSION = "0.1";
	
	static final byte sPACKET = (byte) 0xEF;
	private static final byte sLONG = (byte) 0xEE;
	private static final byte sFLOAT = (byte) 0xED;
//	private static final byte sCHAR = (byte) 0xEC;
	private static final byte sBYTE = (byte) 0xEB;
//	private static final byte sBOOLEAN = (byte) 0xEA;
	private static final byte sSTRING = (byte) 0xE9;
	private static final byte ePACKET = (byte) 0xFF;
	private static final byte eLONG = (byte) 0xFE;
	private static final byte eFLOAT = (byte) 0xFD;
//	private static final byte eCHAR = (byte) 0xFC;
	private static final byte eBYTE = (byte) 0xFB;
//	private static final byte eBOOLEAN = (byte) 0xFA;
	private static final byte eSTRING = (byte) 0xF9;

	// used to flatten data into a byte array
	static ByteArrayOutputStream streamBuilder = new ByteArrayOutputStream();
	static int i = 0; // holds the position in the buffer being used.

	int count; // the number of datas that have been set. 
		
	
	public RemoteInput() {

	}
	
	public RemoteInput(Input i) {
		owner = i.owner;
		xDown = i.xDown;
		yDown = i.yDown;
		x = i.x;
		y = i.y;
		updated = i.updated;
		time = i.time;
		xVector = i.xVector;
		yVector = i.yVector;
		downEvent = i.downEvent;
		changed = i.changed;
	}

	public synchronized static byte[] flatten(RemoteInput p) throws Exception {
		
		streamBuilder.reset();
		
		streamBuilder.write(sPACKET); // start of packet
		streamBuilder.write((byte)p.count); // number of values included in this packet.
		
			streamBuilder.write(sFLOAT); // writing a float.
			streamBuilder.write(ByteArrayBuilder.floatToByteArray(p.xDown)); // get float as bytes
			streamBuilder.write(eFLOAT); // end of this float.
			
			streamBuilder.write(sFLOAT); // writing a float.
			streamBuilder.write(ByteArrayBuilder.floatToByteArray(p.yDown)); // get float as bytes
			streamBuilder.write(eFLOAT); // end of this float.
	
			streamBuilder.write(sFLOAT); // writing a float.
			streamBuilder.write(ByteArrayBuilder.floatToByteArray(p.x)); // get float as bytes
			streamBuilder.write(eFLOAT); // end of this float.
			
			streamBuilder.write(sFLOAT); // writing a float.
			streamBuilder.write(ByteArrayBuilder.floatToByteArray(p.y)); // get float as bytes
			streamBuilder.write(eFLOAT); // end of this float.			
			streamBuilder.write(sFLOAT); // writing a float.
			
			streamBuilder.write(ByteArrayBuilder.floatToByteArray(p.xVector)); // get float as bytes
			streamBuilder.write(eFLOAT); // end of this float.
			
			streamBuilder.write(sFLOAT); // writing a float.
			streamBuilder.write(ByteArrayBuilder.floatToByteArray(p.yVector)); // get float as bytes
			streamBuilder.write(eFLOAT); // end of this float.
			
			streamBuilder.write(sBYTE); // writing a byte.
			streamBuilder.write(p.owner); 
			streamBuilder.write(eBYTE); // end of this byte.
			
			
			streamBuilder.write(sSTRING); // writing a string.
			if(p.time == 0) {
				streamBuilder.write(0x00); // null
			} else {
				streamBuilder.write((Long.toString(p.time)).getBytes()); // get string as bytes
			}
			streamBuilder.write(eSTRING); // end of this string.
		
		streamBuilder.write(ePACKET); // end of packet
		
		return streamBuilder.toByteArray();
	}
	
	public static boolean expand(byte[] b, RemoteInput p) throws Exception {

		if(b[i]!=sPACKET) {
				throw new Exception("Expected value (sPACKET) not found.");
		}
		i++;
		p.count = b[i++]; // going to recieve this many values
		
		if(b[i++]!=sFLOAT) { 
			throw new Exception("Expected value (sFLOAT) not found.");
		}
		p.xDown = ByteArrayBuilder.byteArrayToFloat(new byte[]{b[i++], b[i++], b[i++], b[i++]});
		if(b[i++]!=eFLOAT) {
			throw new Exception("Expected value (eFLOAT) not found.");
		}
		
		if(b[i++]!=sFLOAT) {
			throw new Exception("Expected value (sFLOAT) not found.");
		}
		p.yDown = ByteArrayBuilder.byteArrayToFloat(new byte[]{b[i++], b[i++], b[i++], b[i++]});
		if(b[i++]!=eFLOAT) {
			throw new Exception("Expected value (eFLOAT) not found.");
		}
		
		if(b[i++]!=sFLOAT) { 
			throw new Exception("Expected value (sFLOAT) not found.");
		}
		p.x = ByteArrayBuilder.byteArrayToFloat(new byte[]{b[i++], b[i++], b[i++], b[i++]});
		if(b[i++]!=eFLOAT) {
			throw new Exception("Expected value (eFLOAT) not found.");
		}
		
		if(b[i++]!=sFLOAT) {
			throw new Exception("Expected value (sFLOAT) not found.");
		}
		p.y = ByteArrayBuilder.byteArrayToFloat(new byte[]{b[i++], b[i++], b[i++], b[i++]});
		if(b[i++]!=eFLOAT) {
			throw new Exception("Expected value (eFLOAT) not found.");
		}
		
		if(b[i++]!=sFLOAT) {
			throw new Exception("Expected value (sFLOAT) not found.");
		}
		p.xVector = ByteArrayBuilder.byteArrayToFloat(new byte[]{b[i++], b[i++], b[i++], b[i++]});
		if(b[i++]!=eFLOAT) {
			throw new Exception("Expected value (eFLOAT) not found.");
		}
		
		if(b[i++]!=sFLOAT) {
			throw new Exception("Expected value (sFLOAT) not found.");
		}
		p.yVector = ByteArrayBuilder.byteArrayToFloat(new byte[]{b[i++], b[i++], b[i++], b[i++]});
		if(b[i++]!=eFLOAT) {
			throw new Exception("Expected value (eFLOAT) not found.");
		}
		
		if(b[i++]!=sBYTE) {
			throw new Exception("Expected value (sBYTE) not found.");
		}
		p.owner = b[i++];
		if(b[i++]!=eBYTE) {
			throw new Exception("Expected value (eBYTE) not found.");
		}
		
		
		
		if(b[i++]!=sSTRING) {
			throw new Exception("Expected value (sSTRING) not found.");
		}
		StringBuffer str = new StringBuffer();
		while(b[i]!=eSTRING) {
			str.append((char)b[i++]);
		}
		i++;
		p.time = Long.parseLong(str.toString()) + MultiplayerSystem.m_ClockDifference;
		
		if(b[i++]!=ePACKET) {
			throw new Exception("Expected value (ePACKET) not found.");
		}
		
		if(b[i]==sPACKET) {
			return true;
		}

		i = 0;
		return false;
	}

	public void setXDown(float x) {
		this.xDown = x;
		count++;
	}

	public float getXDown() {
		return xDown;
	}

	public void setYDown(float y) {
		this.yDown = y;
		count++;
	}

	public float getYDown() {
		return yDown;
	}

	public Input getAsInput() {
		return (Input) this;
	}

}
